﻿using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Experimental.Rendering;

[CreateAssetMenu(menuName = "Rendering/My Pipeline")]
public class MyPipelineAsset : RenderPipelineAsset
{
    public enum ShadowMapSize
    {
        _256 = 256,
        _512 = 512,
        _1024 = 1024,
        _2048 = 2048,
        _4096 = 4096,
    }

    [SerializeField]
    bool dynamicBatching;

    [SerializeField]
    bool instancing;

    [SerializeField]
    ShadowMapSize shadowMapSize = ShadowMapSize._1024;

    protected override IRenderPipeline InternalCreatePipeline()
    {
        return new MyPipeline(dynamicBatching, instancing, (int)shadowMapSize);
    }
}

public class MyPipeline : RenderPipeline
{
    CullResults cull;
    CommandBuffer cameraBuffer = new CommandBuffer { name = "Render Camera" };
    CommandBuffer shadowBuffer = new CommandBuffer { name = "Render Shadows" };
    Material errorMaterial;
    DrawRendererFlags drawFlags;
    RenderTexture shadowMap;
    int shadowMapSize;
    int shadowTileCount;

    const int MAX_VISIBLE_LIGHTS = 16;
    const string shadowsHardKeyword = "_SHADOWS_HARD";
    const string shadowsSoftKeyword = "_SHADOWS_SOFT";

    static int visibleLightColorsId = Shader.PropertyToID("_VisibleLightColors");
    static int visibleLightDirectionsOrPositionsId = Shader.PropertyToID("_VisibleLightDirectionsOrPositions");
    static int visibleLightAttenuationsId = Shader.PropertyToID("_VisibleLightAttenuations");
    static int visibleLightSpotDirectionsId = Shader.PropertyToID("_VisibleLightSpotDirections");
    static int lightIndicesOffsetAndCountID = Shader.PropertyToID("unity_LightIndicesOffsetAndCount");
    static int worldToShadowMatricesId = Shader.PropertyToID("_WorldToShadowMatrices");
    static int shadowMapId = Shader.PropertyToID("_ShadowMap");
    static int shadowBiasId = Shader.PropertyToID("_ShadowBias");
    static int shadowDataId = Shader.PropertyToID("_ShadowData");
    static int shadowMapSizeId = Shader.PropertyToID("_ShadowMapSize");
    Vector4[] visibleLightColors = new Vector4[MAX_VISIBLE_LIGHTS];
    Vector4[] visibleLightDirectionsOrPositions = new Vector4[MAX_VISIBLE_LIGHTS];
    Vector4[] visibleLightAttenuations = new Vector4[MAX_VISIBLE_LIGHTS];
    Vector4[] visibleLightSpotDirections = new Vector4[MAX_VISIBLE_LIGHTS];
    Vector4[] shadowData = new Vector4[MAX_VISIBLE_LIGHTS];
    Matrix4x4[] worldToShadowMatrices = new Matrix4x4[MAX_VISIBLE_LIGHTS];

    public MyPipeline(bool dynamicBatching, bool instancing, int shadowMapSize)
    {
        //灯光使用线性强度
        GraphicsSettings.lightsUseLinearIntensity = true;
        if (dynamicBatching)
        {
            drawFlags = DrawRendererFlags.EnableDynamicBatching;
        }
        if (instancing)
        {
            drawFlags |= DrawRendererFlags.EnableInstancing;
        }
        this.shadowMapSize = shadowMapSize;
    }

    public override void Render(ScriptableRenderContext renderContext, Camera[] cameras)
    {
        base.Render(renderContext, cameras);
        foreach (var item in cameras)
        {
            Render(renderContext, item);
        }
    }

    void Render(ScriptableRenderContext context, Camera camera)
    {
        //获取相机的剔除参数
        if (!CullResults.GetCullingParameters(camera, out ScriptableCullingParameters cullingParameters))
            return;
#if UNITY_EDITOR
        if (camera.cameraType == CameraType.SceneView)
        {
            //让SceneView可以显示UGUI
            ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
        }
#endif
        //视椎体剔除
        CullResults.Cull(ref cullingParameters, context, ref cull);

        if (cull.visibleLights.Count > 0)
        {
            ConfigureLights();
            if (shadowTileCount > 0)
            {
                RenderShadowMap(context);
            }
            else
            {
                cameraBuffer.DisableShaderKeyword(shadowsHardKeyword);
                cameraBuffer.DisableShaderKeyword(shadowsSoftKeyword);
            }
        }
        else
        {
            cameraBuffer.SetGlobalVector(lightIndicesOffsetAndCountID, Vector4.zero);
            cameraBuffer.DisableShaderKeyword(shadowsHardKeyword);
            cameraBuffer.DisableShaderKeyword(shadowsSoftKeyword);
        }
        ConfigureLights();

        //启用相机的属性（比如unity_MatrixVP等等）
        context.SetupCameraProperties(camera);
        //清理
        CameraClearFlags clearFlags = camera.clearFlags;
        cameraBuffer.ClearRenderTarget((clearFlags & CameraClearFlags.Depth) != 0, (clearFlags & CameraClearFlags.Color) != 0, camera.backgroundColor);

        cameraBuffer.BeginSample("Render Camera");
        cameraBuffer.SetGlobalVectorArray(visibleLightColorsId, visibleLightColors);
        cameraBuffer.SetGlobalVectorArray(visibleLightDirectionsOrPositionsId, visibleLightDirectionsOrPositions);
        cameraBuffer.SetGlobalVectorArray(visibleLightAttenuationsId, visibleLightAttenuations);
        cameraBuffer.SetGlobalVectorArray(visibleLightSpotDirectionsId, visibleLightSpotDirections);
        context.ExecuteCommandBuffer(cameraBuffer);
        cameraBuffer.Clear();

        //执行shader的SRPDefaultUnlit(SRP需要特定的shader，而unlit中的SRPDefaultUnlit pass支持SRP)
        DrawRendererSettings drawRendererSettings = new DrawRendererSettings(camera, new ShaderPassName("SRPDefaultUnlit"));
        if (cull.visibleLights.Count > 0)
        {
            drawRendererSettings.rendererConfiguration = RendererConfiguration.PerObjectLightIndices8;
        }
        drawRendererSettings.flags = drawFlags;
        //先绘制不透明的shader，RQ=[0,2500]，由前到后渲染
        drawRendererSettings.sorting.flags = SortFlags.CommonOpaque;
        FilterRenderersSettings filterRenderersSettings = new FilterRenderersSettings(true) { renderQueueRange = RenderQueueRange.opaque };
        context.DrawRenderers(cull.visibleRenderers, ref drawRendererSettings, filterRenderersSettings);
        //绘制天空盒（在不透明之后绘制可以减少一些overdraw）
        context.DrawSkybox(camera);
        //绘制透明的shader,RQ=[2501,5000],由后向前渲染(因为透明混合受buffer中的颜色影响)
        drawRendererSettings.sorting.flags = SortFlags.CommonTransparent;
        filterRenderersSettings.renderQueueRange = RenderQueueRange.transparent;
        context.DrawRenderers(cull.visibleRenderers, ref drawRendererSettings, filterRenderersSettings);
        //绘制默认渲染管线
        DrawDefaultPipeline(context, camera);

        cameraBuffer.EndSample("Render Camera");
        context.ExecuteCommandBuffer(cameraBuffer);
        cameraBuffer.Clear();

        context.Submit();

        if (shadowMap)
        {
            RenderTexture.ReleaseTemporary(shadowMap);
            shadowMap = null;
        }
    }

    [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
    //这个主要是把不受SRP支持的shader绘制出来，用默认的错误shader
    void DrawDefaultPipeline(ScriptableRenderContext context, Camera camera)
    {
        if (errorMaterial == null)
        {
            //那个粉紫色的shader
            Shader errorShader = Shader.Find("Hidden/InternalErrorShader");
            errorMaterial = new Material(errorShader)
            {
                hideFlags = HideFlags.HideAndDontSave
            };
        }
        //要绘制哪些pass
        DrawRendererSettings drawRendererSettings = new DrawRendererSettings(camera, new ShaderPassName("ForwardBase"));
        drawRendererSettings.SetShaderPassName(1, new ShaderPassName("PrepassBase"));
        drawRendererSettings.SetShaderPassName(2, new ShaderPassName("Always"));
        drawRendererSettings.SetShaderPassName(3, new ShaderPassName("Vertex"));
        drawRendererSettings.SetShaderPassName(4, new ShaderPassName("VertexLMRGBM"));
        drawRendererSettings.SetShaderPassName(5, new ShaderPassName("VertexLM"));
        //覆盖材质
        drawRendererSettings.SetOverrideMaterial(errorMaterial, 0);
        FilterRenderersSettings filterRenderersSettings = new FilterRenderersSettings(true);
        context.DrawRenderers(cull.visibleRenderers, ref drawRendererSettings, filterRenderersSettings);
    }

    void ConfigureLights()
    {
        shadowTileCount = 0;
        for (int i = 0; i < cull.visibleLights.Count; i++)
        {
            if (i == MAX_VISIBLE_LIGHTS)
                break;
            Vector4 attenuation = Vector4.zero;
            attenuation.w = 1;
            Vector4 shadow = Vector4.zero;
            VisibleLight light = cull.visibleLights[i];
            if (light.lightType == LightType.Directional)
            {
                Vector4 v = light.localToWorld.GetColumn(2);
                v.x = -v.x;
                v.y = -v.y;
                v.z = -v.z;
                visibleLightDirectionsOrPositions[i] = v;
            }
            else
            {
                visibleLightDirectionsOrPositions[i] = light.localToWorld.GetColumn(3);
                attenuation.x = 1f / Mathf.Max(light.range * light.range, 0.00001f);
                if (light.lightType == LightType.Spot)
                {
                    Vector4 v = light.localToWorld.GetColumn(2);
                    v.x = -v.x;
                    v.y = -v.y;
                    v.z = -v.z;
                    visibleLightSpotDirections[i] = v;
                    float outerRed = Mathf.Deg2Rad * 0.5f * light.spotAngle;
                    float outCos = Mathf.Cos(outerRed);
                    float outTan = Mathf.Tan(outerRed);
                    float innerCos = Mathf.Cos(Mathf.Atan((46f / 64f) * outTan));
                    float angleRange = Mathf.Max(innerCos - outCos, 0.001f);
                    attenuation.z = 1f / angleRange;
                    attenuation.w = -outCos * attenuation.z;

                    Light shadowLight = light.light;
                    Bounds shadowBounds;
                    if (shadowLight.shadows != LightShadows.None && cull.GetShadowCasterBounds(i, out shadowBounds))
                    {
                        shadowTileCount += 1;
                        shadow.x = shadowLight.shadowStrength;
                        shadow.y = shadowLight.shadows == LightShadows.Soft ? 1 : 0;
                    }
                }

            }
            visibleLightAttenuations[i] = attenuation;
            visibleLightColors[i] = light.finalColor;
            shadowData[i] = shadow;
        }
    }

    void RenderShadowMap(ScriptableRenderContext context)
    {
        int split;
        if (shadowTileCount <= 1)
        {
            split = 1;
        }
        else if (shadowTileCount <= 4)
        {
            split = 2;
        }
        else if (shadowTileCount <= 9)
        {
            split = 3;
        }
        else
        {
            split = 4;
        }

        float tileSize = shadowMapSize / split;
        float tileScale = 1f / split;
        Rect tileViewport = new Rect(0f, 0f, tileSize, tileSize);

        shadowMap = RenderTexture.GetTemporary(shadowMapSize, shadowMapSize, 16, RenderTextureFormat.Shadowmap);
        shadowMap.filterMode = FilterMode.Bilinear;
        shadowMap.wrapMode = TextureWrapMode.Clamp;

        CoreUtils.SetRenderTarget(shadowBuffer, shadowMap, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store, ClearFlag.Depth);
        shadowBuffer.BeginSample("Render Shadows");
        context.ExecuteCommandBuffer(shadowBuffer);
        shadowBuffer.Clear();

        int tileIndex = 0;
        bool hardShadows = false;
        bool softShadows = false;
        for (int i = 0; i < cull.visibleLights.Count; i++)
        {
            if (i == MAX_VISIBLE_LIGHTS)
                break;

            if (shadowData[i].x <= 0f)
                continue;

            Matrix4x4 viewMatrix, projectionMatrix;
            ShadowSplitData splitData;
            if (!cull.ComputeSpotShadowMatricesAndCullingPrimitives(i, out viewMatrix, out projectionMatrix, out splitData))
            {
                shadowData[i].x = 0f;
                continue;
            }

            float tileOffsetX = tileIndex % split;
            float tileOffsetY = tileIndex / split;
            tileViewport.x = tileOffsetX * tileSize;
            tileViewport.y = tileOffsetY * tileSize;

            if (split > 1)
            {
                shadowBuffer.SetViewport(tileViewport);
                shadowBuffer.EnableScissorRect(new Rect(tileViewport.x + 4f, tileViewport.y + 4f, tileSize - 8f, tileSize - 8f));
            }
            shadowBuffer.SetViewProjectionMatrices(viewMatrix, projectionMatrix);
            shadowBuffer.SetGlobalFloat(shadowBiasId, cull.visibleLights[i].light.shadowBias);
            context.ExecuteCommandBuffer(shadowBuffer);
            shadowBuffer.Clear();

            var shadowSettings = new DrawShadowsSettings(cull, i);
            context.DrawShadows(ref shadowSettings);

            if (SystemInfo.usesReversedZBuffer)
            {
                projectionMatrix.m20 = -projectionMatrix.m20;
                projectionMatrix.m21 = -projectionMatrix.m21;
                projectionMatrix.m22 = -projectionMatrix.m22;
                projectionMatrix.m23 = -projectionMatrix.m23;
            }
            var scaleOffset = Matrix4x4.TRS(Vector3.one * 0.5f, Quaternion.identity, Vector3.one * 0.5f);
            worldToShadowMatrices[i] = scaleOffset * (projectionMatrix * viewMatrix);

            if (split > 1)
            {
                var tileMatrix = Matrix4x4.identity;
                tileMatrix.m00 = tileMatrix.m11 = tileScale;
                tileMatrix.m03 = tileOffsetX * tileScale;
                tileMatrix.m13 = tileOffsetY * tileScale;
                worldToShadowMatrices[i] = tileMatrix * worldToShadowMatrices[i];
            }
            tileIndex += 1;

            if (shadowData[i].y <= 0f)
            {
                hardShadows = true;
            }
            else
            {
                softShadows = true;
            }
        }
        if (split > 1)
        {
            shadowBuffer.DisableScissorRect();
        }
        shadowBuffer.SetGlobalTexture(shadowMapId, shadowMap);
        shadowBuffer.SetGlobalMatrixArray(worldToShadowMatricesId, worldToShadowMatrices);
        shadowBuffer.SetGlobalVectorArray(shadowDataId, shadowData);
        float invShadowMapSize = 1f / shadowMapSize;
        shadowBuffer.SetGlobalVector(shadowMapSizeId, new Vector4(invShadowMapSize, invShadowMapSize, shadowMapSize, shadowMapSize));
        CoreUtils.SetKeyword(shadowBuffer, shadowsHardKeyword, hardShadows);
        CoreUtils.SetKeyword(shadowBuffer, shadowsSoftKeyword, softShadows);

        shadowBuffer.EndSample("Render Shadows");
        context.ExecuteCommandBuffer(shadowBuffer);
        shadowBuffer.Clear();
    }

    public override void Dispose()
    {
        cameraBuffer.Release();
        cameraBuffer = null;
        base.Dispose();
    }
}
